package com.yuelan.apartment.service.impl;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;

import com.baomidou.mybatisplus.core.batch.MybatisBatch;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.yuelan.apartment.domain.ApaRentBill;
import com.yuelan.apartment.domain.ApaRentOverdue;
import com.yuelan.apartment.domain.dto.ApaRentBillDto;
import com.yuelan.apartment.domain.vo.ApaRentBillVo;
import com.yuelan.apartment.mapper.ApaRentBillMapper;
import com.yuelan.apartment.mapper.ApaRentOverdueMapper;
import com.yuelan.apartment.service.ApaRentService;
import com.yuelan.common.constant.HttpStatus;
import com.yuelan.common.core.page.TableDataInfo;
import com.yuelan.common.core.redis.RedisCache;
import com.yuelan.common.exception.GlobalException;
import com.yuelan.common.utils.DateUtils;
import com.yuelan.common.utils.SecurityUtils;
import com.yuelan.common.utils.bean.BeanUtils;
import com.yuelan.common.utils.uuid.IdWorker;

import org.apache.ibatis.session.SqlSessionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.yuelan.apartment.common.BillType.*;

/**
 * @description: 租金管理
 * @Author: ZhaoYi
 * @date: 24-6-21 22:33
 */
@Service
public class ApaRentServiceImpl extends ServiceImpl<ApaRentBillMapper, ApaRentBill> implements ApaRentService {

    @Resource
    private ApaRentBillMapper apaRentBillMapper;
    @Resource
    private ApaRentOverdueMapper apaRentOverdueMapper;
    @Resource
    private SqlSessionFactory sqlSessionFactory;
    @Resource
    @Qualifier("updateBillOverdueExecutor")
    private ThreadPoolExecutor updateBillOverdueExecutor;
    @Resource
    private RedisCache redisCache;

    private static final Logger logger = LoggerFactory.getLogger(GlobalException.class);


    /**
     * @speed: 40-90ms
     * @des: 处理账单逾期 （批量更新+手动事务）
     */
    @Override
    @Transactional
    public void updateBillOverdue() {
        // 查询当前时间大于租金缴费时间的过期账单
        LambdaQueryWrapper<ApaRentBill> rentBillLambdaQueryWrapper = new LambdaQueryWrapper<ApaRentBill>()
                .lt(ApaRentBill::getCollectionTime, DateUtils.getNowDate());
        List<ApaRentBill> apaRentBills = apaRentBillMapper.selectList(rentBillLambdaQueryWrapper);
        // 对过期账单进行批量更新状态
        List<ApaRentBill> billsToUpdate = apaRentBills.stream().peek(bill -> {
                bill.setStatus(Overdue.getStatus());
                bill.setUpdateTime(DateUtils.getNowDate());
        }).collect(Collectors.toList());
        super.updateBatchById(billsToUpdate);
    }


    /**
     * @speed: 25-60ms
     * @des: 基于两个CountDownLatch控制多线程事务提交
     */
    @Override
    @Transactional(rollbackFor = Throwable.class)
    public void updateBillOverdueCountDownLatch() {
        LambdaQueryWrapper<ApaRentBill> rentBillLambdaQueryWrapper = new LambdaQueryWrapper<ApaRentBill>()
                .lt(ApaRentBill::getCollectionTime, DateUtils.getNowDate());
        List<ApaRentBill> apaRentBills = apaRentBillMapper.selectList(rentBillLambdaQueryWrapper);

        final int threadCount = updateBillOverdueExecutor.getCorePoolSize();
        CountDownLatch threadLatch = new CountDownLatch(threadCount);

        final int dataLength = (apaRentBills.size() + threadCount - 1) / threadCount;
        for (int i = 0; i < threadCount; i++) {
            final List<ApaRentBill> threadData = apaRentBills.stream()
                    .skip(i * dataLength).limit(dataLength)
                    .collect(Collectors.toList());
            updateBillOverdueExecutor.execute(() -> {
                try {
                    updateBillThreadsAndTrans(threadData);
                }
                catch (Throwable e) {
                    e.printStackTrace();
                    logger.error(e.toString());
                }
                threadLatch.countDown();
            });
        }
        // 等待所有线程完成
        try {
            threadLatch.await();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 保留中断状态
            logger.error(e.toString());
        }
//        updateBillOverdueExecutor.shutdown();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Throwable.class)
    public void updateBillThreadsAndTrans(List<ApaRentBill> apaRentBills) {
        try {
            List<ApaRentBill> billList = apaRentBills.stream()
                    .peek(bill -> {
                        bill.setStatus(Overdue.getStatus());
                        bill.setUpdateTime(DateUtils.getNowDate());
            }).collect(Collectors.toList());
            super.updateBatchById(billList);
        }
        catch (Throwable e) {
            logger.error("更新账单时发生错误", e);
            throw e;
        }

    }


    /**
     * 租金账单生成
     * @param rentBillDto
     */
    @Override
    @Transactional
    public void createRentBill(ApaRentBillDto rentBillDto) {
        // 租户合同到期时间
        Date contractEndDate = rentBillDto.getContractEndDate();
        // 计算租户入住起当前月份到合同到期有几个月
        long between = ChronoUnit.MONTHS.between
                (DateUtils.getNowDate().toInstant().atZone(ZoneId.of("UTC")).toLocalDate(),
                contractEndDate.toInstant().atZone(ZoneId.of("UTC")).toLocalDate());

        // 租户入住时间起，生成当前到合同到期的账单
        List<ApaRentBill> apaRentBillList = new ArrayList<>();
        for (int i = 1; i <= between; i++) {
            DateTime date = DateUtil.offsetMonth(DateUtil.date() , i);
            rentBillDto.setCollectionTime(date.toJdkDate());
            apaRentBillList.add(getRentBill(rentBillDto, i));
        }

        // 批量插入账单
        MybatisBatch<ApaRentBill> billBatch = new MybatisBatch<>(sqlSessionFactory , apaRentBillList);
        MybatisBatch.Method<ApaRentBill> method = new MybatisBatch.Method<>(ApaRentBillMapper.class);
        billBatch.execute(method.insert());
    }

    private ApaRentBill getRentBill(ApaRentBillDto rentBillDto, int cycle) {
        ApaRentBill apaRentBill = new ApaRentBill();
        apaRentBill.setId(IdWorker.snowflake());
        apaRentBill.setCycle(cycle);
        apaRentBill.setApartmentId(rentBillDto.getApartmentId());
        apaRentBill.setRoomNum(rentBillDto.getRoomNum());
        apaRentBill.setTenantId(rentBillDto.getTenantId());
        apaRentBill.setCollectionTime(rentBillDto.getCollectionTime());
        apaRentBill.setRent(rentBillDto.getRent());
        apaRentBill.setPaymentType(rentBillDto.getPaymentType());
        apaRentBill.setStatus(rentBillDto.getStatus());
        apaRentBill.setCreateTime(rentBillDto.getCreateTime());
        return apaRentBill;
    }


    /**
     * 统计今/往年账单收益
     * @param year
     */
    @Override
    public List<BigDecimal> yearProfit(String year) {
        ArrayList<BigDecimal> res = new ArrayList<>();
        for (int i = 1; i <= 12; i++) {
            LambdaQueryWrapper<ApaRentBill> rentBillWrapper = new LambdaQueryWrapper<>();
            rentBillWrapper.in(ApaRentBill::getCreateBy, SecurityUtils.getUsername());
            rentBillWrapper.eq(ApaRentBill::getStatus, Collected.getStatus());
            String date = "year(collection_time) = " + year + " and month(collection_time) = " + i;
            rentBillWrapper.apply(date);
            BigDecimal reduce = apaRentBillMapper.selectList(rentBillWrapper)
                    .stream().map(ApaRentBill::getRent).reduce(BigDecimal.ZERO , BigDecimal::add);
            res.add(reduce);
        }
        return res;
    }


    /**
     * 账单状态
     * @param type
     * @return 假分页
     */
    @Override
    public TableDataInfo billStatus(String type, int pageNum, int pageSize) {
        //查询账单
        List<ApaRentBillVo> billVoList = apaRentBillMapper
                .selectBillStatus(SecurityUtils.getUsername() , DateTime.now() , type);

        if (billVoList == null || billVoList.size() == 0) {
            TableDataInfo rspData = new TableDataInfo();
            rspData.setRows(Collections.emptyList());
            return rspData;
        }
        // 查询逾期账单
        List<Long> billIds = billVoList.stream().map(ApaRentBillVo::getId).collect(Collectors.toList());
        LambdaQueryWrapper<ApaRentOverdue> overdueWrapper = new LambdaQueryWrapper<>();
        overdueWrapper.in(ApaRentOverdue::getBillId, billIds);
        List<ApaRentOverdue> apaRentOverdueList = apaRentOverdueMapper.selectList(overdueWrapper);
        Map<Long, List<ApaRentOverdue>> overdueIdsMap = apaRentOverdueList.stream()
                .collect(Collectors.groupingBy(ApaRentOverdue::getBillId));

        List<ApaRentBillVo> billRes = billVoList
                .stream().map(bill -> {
                    ApaRentBillVo apaRentBillVo = new ApaRentBillVo();
                    BigDecimal total = bill.getRentMoney();
                    BeanUtils.copyBeanProp(apaRentBillVo , bill);
                    List<ApaRentOverdue> overdueList = overdueIdsMap.getOrDefault(bill.getId() ,
                            Collections.emptyList());
                    apaRentBillVo.setOverdueDay(0);
                    apaRentBillVo.setOverdueMoney(BigDecimal.ZERO);
                    for (ApaRentOverdue overdue : overdueList) {
                        total = total.add(overdue.getOverdueAmount());
                        apaRentBillVo.setOverdueDay(overdue.getOverdueDay());
                        apaRentBillVo.setOverdueMoney(overdue.getOverdueAmount());
                    }
                    apaRentBillVo.setTotal(total);
                    apaRentBillVo.setStatus(getBillStatus(bill.getStatus()));
                    return apaRentBillVo;
                }).collect(Collectors.toList());

        return pageRes(billRes, pageNum, pageSize);
    }

    private TableDataInfo pageRes(List<ApaRentBillVo> data, int pageNum, int pageSize) {
        List<ApaRentBillVo> row = data
                .stream().skip((pageNum - 1) * pageSize).limit(pageSize).collect(Collectors.toList());
        TableDataInfo rspData = new TableDataInfo();
        rspData.setCode(HttpStatus.SUCCESS);
        rspData.setRows(row);
        rspData.setTotal(data.size());
        return rspData;
    }

    private String getBillStatus(String status) {
        if (Uncollected.getStatus().equals(status))
            return Uncollected.getRes();
        if (Collected.getStatus().equals(status))
            return Collected.getRes();
        if (Overdue.getStatus().equals(status))
            return Overdue.getRes();
        return status;
    }

    // 统计分页数据
    private long total(List<Long> houseIds, String billStatus) {
        LambdaQueryWrapper<ApaRentBill> billCountWrapper = new LambdaQueryWrapper<>();
        billCountWrapper.in(ApaRentBill::getApartmentId, houseIds);
        billCountWrapper.eq(ApaRentBill::getStatus, billStatus);
        String condition = "MONTH(collection_time) = MONTH('" + DateTime.now() + "')";
        billCountWrapper.apply(condition);
        return apaRentBillMapper.selectCount(billCountWrapper);
    }


}
